﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using BLToolkit.Data;
using BLToolkit.Data.Linq;
using BLToolkit.Data.Sql;
using BLToolkit.DataAccess;
using BLToolkit.Mapping;
using BLToolkit.Reflection;
using BLToolkit.ServiceModel;
using Devsoft.DB.Models.BltookitEx;
using SqlQuery = BLToolkit.Data.Sql.SqlQuery;

namespace Devsoft.DB.Models
{
	public class Manager<T> : HybridTable<T> where T : class 
    {
        #region Constructors
        
        public Manager(DbManager dbManager)
            : base(dbManager)
        {
            _DbManager = dbManager;
        }

        #endregion

        #region Public Methods

        public List<T> All
        {
            get
            {
                return GetAll();
            }
        }

	    public string TableName
	    {
            get { return GetTableName(); }
	    }


	    public LinqServiceResult Query(LinqServiceQuery query)
        {
            var db = this.DataContextInfo.DataContext;
            var obj = db.SetQuery(new QueryContext { SqlQuery = query.Query, Parameters = query.Parameters });
            using (var rd = db.ExecuteReader(obj))
            {
                var ret = new LinqServiceResult
                {
                    QueryID = Guid.NewGuid(),
                    FieldCount = rd.FieldCount,
                    FieldNames = new string[rd.FieldCount],
                    FieldTypes = new Type[rd.FieldCount],
                    Data = new List<string[]>(),
                };

                for (var i = 0; i < ret.FieldCount; i++)
                {
                    ret.FieldNames[i] = rd.GetName(i);
                    ret.FieldTypes[i] = rd.GetFieldType(i);
                }

                var varyingTypes = new List<Type>();

                while (rd.Read())
                {
                    var data = new string[rd.FieldCount];
                    var codes = new TypeCode[rd.FieldCount];

                    for (var i = 0; i < ret.FieldCount; i++)
                        codes[i] = Type.GetTypeCode(ret.FieldTypes[i]);

                    ret.RowCount++;

                    for (var i = 0; i < ret.FieldCount; i++)
                    {
                        if (!rd.IsDBNull(i))
                        {
                            var code = codes[i];
                            var type = rd.GetFieldType(i);
                            var idx = -1;

                            if (type != ret.FieldTypes[i])
                            {
                                code = Type.GetTypeCode(type);
                                idx = varyingTypes.IndexOf(type);

                                if (idx < 0)
                                {
                                    varyingTypes.Add(type);
                                    idx = varyingTypes.Count - 1;
                                }
                            }

                            switch (code)
                            {
                                case TypeCode.Decimal: data[i] = rd.GetDecimal(i).ToString(CultureInfo.InvariantCulture); break;
                                case TypeCode.Double: data[i] = rd.GetDouble(i).ToString(CultureInfo.InvariantCulture); break;
                                case TypeCode.Single: data[i] = rd.GetFloat(i).ToString(CultureInfo.InvariantCulture); break;
                                case TypeCode.DateTime: data[i] = rd.GetDateTime(i).ToString(CultureInfo.InvariantCulture); break;
                                default:
                                    {
                                        if (ret.FieldTypes[i] == typeof(byte[]))
                                            data[i] = Convert.ToBase64String((byte[])rd.GetValue(i));
                                        else
                                            data[i] = (rd.GetValue(i) ?? "").ToString();

                                        break;
                                    }
                            }

                            if (idx >= 0)
                                data[i] = "\0" + (char)idx + data[i];
                        }
                    }

                    ret.Data.Add(data);
                }

                ret.VaryingTypes = varyingTypes.ToArray();

                return ret;
            }
        }

        public T Get(object key)
        {
            return Get(false, key);
        }

        public T Get(bool mustExist, object key)
        {
            T item = new SqlQueryEx<T>(_DbManager).SelectByKey(key);
            if (mustExist && null == item)
            {
                throw new Exception(string.Format("{0} with key {1} doest not exist", typeof(T).Name, key));
            }
            return item;
        }
        
        public virtual List<T> GetAll()
        {
            return new SqlQueryEx<T>(_DbManager).SelectAll();
        }

        public virtual void Delete(T item)
        {
            new SqlQueryEx<T>(_DbManager).Delete(item);
        }

        public virtual void Delete(int batchSize, IEnumerable<T> items)
        {
            new SqlQueryEx<T>(_DbManager).Delete(batchSize,items);
        }

		public virtual void DeleteByKey(params object[] keys)
		{
			new SqlQueryEx<T>(_DbManager).DeleteByKey(keys);
		}




		public virtual void Delete(Expression<Func<T, bool>> expression)
		{
			_DbManager.GetTable<T>().Delete(expression);
		}

		public virtual void Delete(IEnumerable<int> ids)
		{
			var query = string.Format("DELETE FROM {0} WHERE ID IN ({1})",
			                          GetTableName(), string.Join(",", ids));
			_DbManager.SetCommand(query).ExecuteNonQuery();
		}


        public virtual int Insert(T item)
        {
            return new SqlQueryEx<T>(_DbManager).Insert(item);
        }
        
        public virtual void Insert(IEnumerable<T> items)
        {
            Insert(items.ToArray());
        }

        public virtual void Insert(int batchSize,IEnumerable<T> items)
        {
            Insert(batchSize,items.ToArray());
        }



        public virtual void Insert(int batchSize,params T[] items)
        {
            new SqlQuery<T>(_DbManager).Insert(batchSize,items);
        }

        public virtual void Insert(params T[] items)
        {
            Insert(Int32.MaxValue, items);
        }

        public virtual object InsertWithIdentity(T item)
        {
            return _DbManager.InsertWithIdentity(item);
            /*
            _DbManager.InsertOrReplace()
            var table = new Table<T>(_DbManager);
            table.Insert(() => item);
            return new SqlQueryEx<T>(_DbManager).InsertWithIdentity(item);*/
        }

        public virtual bool Update(T item)
        {
            return new SqlQueryEx<T>(_DbManager).Update(item) > 0;
        }

        public void Update(int batchSize, IEnumerable<T> items)
        {
            new SqlQuery<T>(_DbManager).Update(batchSize, items);
        }

        public virtual int Count()
        {
            return _DbManager.SetCommand(string.Format("select count(*) from {0}",GetTableName())).ExecuteScalar<int>();
        }

        public virtual void Truncate()
        {
            _DbManager.SetCommand(string.Format("truncate table {0}", GetTableName())).ExecuteNonQuery();
        }

        public virtual IEnumerable<T> ExcecuteRawQuery(string sql, params object[] paramters)
        {
            var db = GetDbManager();
            

            var om    = _DbManager.MappingSchema.GetObjectMapper(typeof(T));
			var query = new SqlQueryInfo(om);
            query.QueryText = sql;

            db.SetCommand(sql, query.GetParameters(_DbManager, paramters));
            db.Command.CommandTimeout = 0;
            return ExecuteEnumerable<T>(db, DisposeDbManager);

            /*
            var cmd = _DbManager.SetCommand(sql, query.GetParameters(_DbManager, paramters));
            cmd.Command.CommandTimeout = 0;
            using (var reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return _DbManager.MappingSchema.MapDataReaderToObject<T>(reader);
                }
            }*/
        }

        public virtual IEnumerable<T> Fetch(int[] ids)
        {
            if (ids.Length < 1)
            {
                return new List<T>();
            }
            return ExcecuteRawQuery(string.Format("select * from {0} where ID in ({1})", GetTableName(),
                                               string.Join(",", ids.Select(x => x.ToString()))));
        }

        #endregion

        #region Protected Methods

        protected  string GetTableName()
        {
            bool isSet;
            string str = _DbManager.MappingSchema.MetadataProvider.GetTableName(typeof(T), null, out isSet);
            return str;
        }

        #endregion

        class QueryContext : IQueryContext
        {
            public SqlQuery SqlQuery { get; set; }
            public object Context { get; set; }
            public SqlParameter[] Parameters { get; set; }

            public SqlParameter[] GetParameters()
            {
                return Parameters;
            }
        }


        protected DbManager _DbManager;
    }

    public static class DbManagerExtention
    {
        public static IEnumerable<T> ExecuteEnumerator<T>(this DbManager dbManager, string sql, params object[] paramters)
        {
            var om = dbManager.MappingSchema.GetObjectMapper(typeof(T));
            var query = new SqlQueryInfo(om);
            query.QueryText = sql;

            var cmd = dbManager.SetCommand(sql, query.GetParameters(dbManager, paramters));
            cmd.Command.CommandTimeout = 0;
            using (var reader = cmd.ExecuteReader())
            {
                while (reader.Read())
                {
                    yield return (T)reader.GetValue(0);
                }
            }

        }
    }
}
